package com.kaibes.orm.core.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.kaibes.orm.core.dialect.Dialect;
import com.kaibes.orm.core.listener.CacheListener;
import com.kaibes.orm.core.listener.DeleteListener;
import com.kaibes.orm.core.listener.InsertListener;
import com.kaibes.orm.core.listener.SelectListener;
import com.kaibes.orm.core.listener.UpdateListener;
import com.kaibes.orm.curd.Delete;
import com.kaibes.orm.curd.Insert;
import com.kaibes.orm.curd.Select;
import com.kaibes.orm.curd.Update;
import com.kaibes.orm.curd.Wrapper;

public abstract class Mapper<E> extends TableParser<E> {
    
    private List<InsertListener<?>> insertListenerList = new ArrayList<>();
    private List<DeleteListener> deleteListenerList = new ArrayList<>();
    private List<UpdateListener> updateListenerList = new ArrayList<>();
    private List<SelectListener<?>> selectListenerList = new ArrayList<>();
    private CacheListener<E> cacheListener;
    
    public Mapper(Dialect dialect, SqlRunner sqlRunner) {
        super(dialect, sqlRunner);
    }
    
    private void checkTableName(Wrapper wrapper) {
        if (wrapper.getTableName() == null) {
            wrapper.setTableName(tableName);
        }
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public <T> List<T> insert(Insert insert) {
        if (insert.getDatas().isEmpty()) {
            return null;
        }
        checkTableName(insert);
        Object transaction = openTransaction();
        try {
            for (int i = 0; i < insertListenerList.size(); i++) {
                InsertListener<?> listener = insertListenerList.get(i);
                if (listener.isWorking()) {
                    listener.beforeInsert(insert);
                }
            }
            List<T> result = super.insert(insert);
            for (int i = 0; i < insertListenerList.size(); i++) {
                InsertListener listener = insertListenerList.get(i);
                if (listener.isWorking()) {
                    listener.afterInsert(insert, result);
                }
            }
            if (cacheListener != null) {
                cacheListener.clear();
            }
            commit(transaction);
            return result;
        } catch (Exception e) {
            rollback(transaction);
            e.printStackTrace();
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }
    
    @Override
    public int delete(Delete delete) {
        if (delete.getQueries().isEmpty()) {
            return -1;
        }
        checkTableName(delete);
        Object transaction = openTransaction();
        try {
            for (int i = 0; i < deleteListenerList.size(); i++) {
                DeleteListener listener = deleteListenerList.get(i);
                if (listener.isWorking()) {
                    listener.beforeDelete(delete);
                }
            }
            int result = super.delete(delete);
            for (int i = 0; i < deleteListenerList.size(); i++) {
                DeleteListener listener = deleteListenerList.get(i);
                if (listener.isWorking()) {
                    listener.afterDelete(delete, result);
                }
            }
            if (cacheListener != null) {
                cacheListener.clear();
            }
            commit(transaction);
            return result;
        } catch (Exception e) {
            rollback(transaction);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }
    
    @Override
    public int update(Update update) {
        if (update.getQueries().isEmpty() || update.getData().isEmpty()) {
            return -1;
        }
        checkTableName(update);
        Object transaction = openTransaction();
        try {
            for (int i = 0; i < updateListenerList.size(); i++) {
                UpdateListener listener = updateListenerList.get(i);
                if (listener.isWorking()) {
                    listener.beforeUpdate(update);
                }
            }
            int result = super.update(update);
            for (int i = 0; i < updateListenerList.size(); i++) {
                UpdateListener listener = updateListenerList.get(i);
                if (listener.isWorking()) {
                    listener.afterUpdate(update, result);
                }
            }
            if (cacheListener != null) {
                cacheListener.clear();
            }
            commit(transaction);
            return result;
        } catch (Exception e) {
            rollback(transaction);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public <T> List<T> select(Select select, Class<T> targetClass) {
        checkTableName(select);
        Object transaction = openTransaction();
        try {
            for (int i = 0; i < selectListenerList.size(); i++) {
                SelectListener<?> listener = selectListenerList.get(i);
                if (listener.isWorking() && listener.isSupports(targetClass)) {
                    listener.beforeSelect(select);
                }
            }
            List<T> result = null;
            boolean isCached = false;
            // cache
            if (cacheListener != null) {
                if (Number.class.isAssignableFrom(targetClass)) {
                    Long l = cacheListener.getCount(select);
                    if (l != null) {
                        result = new ArrayList<>();
                        result.add((T) l);
                    }
                } else {
                    result = cacheListener.get(select);
                }
                if (result != null) {
                    isCached = true;
                    commit(transaction);
                    return result;
                }
            }
            result = super.select(select, targetClass);
            for (int i = 0; i < selectListenerList.size(); i++) {
                SelectListener<?> listener = selectListenerList.get(i);
                if (listener.isWorking() && listener.isSupports(targetClass)) {
                    listener.afterSelect(select, result);
                }
            }
            // cache
            if (cacheListener != null && !isCached) {
                if (Number.class.isAssignableFrom(targetClass)) {
                    cacheListener.setCount(select, (Long) result.get(0));
                } else {
                    cacheListener.set(select, result);
                }
            }
            commit(transaction);
            return result;
        } catch (Exception e) {
            rollback(transaction);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }
    
    // ==================================
    protected abstract Object openTransaction();
    protected abstract void commit(Object transaction);
    protected abstract void rollback(Object transaction);
    
    // ======
    public Page<E> page(Select select, int pageIndex, int pageSize) {
        Page<E> page = new Page<>(pageIndex, pageSize);
        initPageTotal(page, select);
        if (pageIndex > page.getTotalPage()) {
            page.setPageIndex(page.getTotalPage());
        }
        initPageData(page, select);
        return page;
    }

    public void initPageTotal(Page<E> page, Select select) {
        long count = count(select);
        page.setTotalCount(count);
        long pageCount = count / page.getPageSize();
        if (count % page.getPageSize() != 0) {
            pageCount++;
        }
        page.setTotalPage(pageCount);
    }

    public void initPageData(Page<E> page, Select select) {
        if (page.getPageIndex() == 0) {
            page.setData(Collections.emptyList());
        } else {
            select.setOffset((page.getPageIndex() - 1) * page.getPageSize());
            select.setSize(page.getPageSize());
            page.setData(select(select));
        }
    }
    
    // ============
    public void addInsertListener(InsertListener<?> listener) {
        this.insertListenerList.add(listener);
    }

    public void addDeleteListener(DeleteListener listener) {
        this.deleteListenerList.add(listener);
    }

    public void addUpdateListener(UpdateListener listener) {
        this.updateListenerList.add(listener);
    }

    public void addSelectListener(SelectListener<?> listener) {
        this.selectListenerList.add(listener);
    }
    
    // ====================
    public CacheListener<E> getCacheListener() {
        return cacheListener;
    }
    public void setCacheListener(CacheListener<E> cacheListener) {
        this.cacheListener = cacheListener;
    }

}
